#!/usr/bin/env python

import re
import BeautifulSoup
import lf_constants
import lf_base
import lf_ocr

class FacebookProfilePage(object):
	""" Note: the get* functions will return whatever is listed on the profile
	page, which may or may not be all the data for that type.
	Example: getPhotos will not return all albums or photos."""

	@classmethod
	def _noColon(self, tag):
		return tag.string[:-1]

	@classmethod
	def _defaultJoin(self, li):
		return ('%s ' % lf_constants.DEFAULT_SEPERATOR).join(li)

	def __init__(self, data):
		self.soup = BeautifulSoup.BeautifulSoup(data)
		self.ocr = lf_ocr.OcrInterface()

	def parsePage(self, boxes=True):
		""" Initial parsing of a page -- gets basic profile into.  
		self.boxes needs to be instantiated if further parsing of 
		the page is to be done. """
		self.attrs = self.getBasicInfo()
		if boxes:
			self.boxes = self.getBoxes()

	def getBasicInfo(self):
		""" Instantiate the attr hash with basic info at the top of the profile """
		basic = {}
		rows = self.soup.find('div', {'class' : 'basic_info'}).findAll('tr')
		for row in rows:
			key = self._noColon(row.find('td'))
			if row.has_key('id'): # Single value
				value = row.findAll('a')
				if len(value) == 1: # Most things
					value = value[0].string
				else: # Birthday, Hometown, Relationship Status
					value = '%s, %s' % (value[0].string, value[1].string)
			else: # Multivalued
				value = []
				for anchor in row.findAll('a'):
					value.append(anchor.string)
			basic[key] = value
		return basic

	def getBoxes(self):
		""" Return a hash of profile box names to their BS tag objects """
		box_names_and_roots = {}
		profile_boxes = self.soup.findAll('div', {'class' : re.compile('^box_head')})
		for profile_box in profile_boxes:
			h2 = profile_box.find('h2')
			if len(h2.contents) == 2: # non-app
				box_title = h2.contents[1]
			else: # app
				box_title = h2.a.string
			box_names_and_roots[box_title] = profile_box.findParent()
		return box_names_and_roots

	def getEducationInfo(self, table=None):
		""" Add educational info to the attr hash """
		# TODO probably overwrites values for college, grad school, etc.
		# if more than one school is listed (for example, transfer students)
		if table:
			rows = table.findAll('tr')
		else:
			try:
				root = self.boxes['Education']
			except:
				raise lf_base.FacebookAttributeFetchError, 'Education'
			rows = root.findAll('tbody')[1].findAll('tr')
		education = {}
		for tr in rows:
			tds = tr.findAll('td')
			key = self._noColon(tds[0])
			ul = tds[1].find('ul')
			lis = ul.findAll('li')
			school = lis[0].findAll('a')
			if school: # linked
				school_name = school[0].string
				if len(school) == 2: 
					class_year = school[1].string
				else:
					class_year = None
			else: # not linked
				spans = lis[0].findAll('span')
				school_name = spans[0].string
				if len(spans) == 2: 
					class_year = spans[1].string
				else:
					class_year = None
			if len(lis) == 2:
				con_list = [a.string for a in lis[1].findAll('a')]
				concentrations = self._defaultJoin(con_list)
				if concentrations == "":
					concentrations = None
			else:
				concentrations = None
			education[key] = F_School(name=school_name, year=class_year, concentrations=concentrations)
		self.attrs.update(education)

	def getWorkInfo(self, tables=None):
		""" Add work info to the attr hash """
		if tables:
			all_tables = tables
		else:
			try:
				root = self.boxes['Work']
			except:
				raise lf_base.FacebookAttributeFetchError, 'Work'
			all_tables = root.findAll('table', {'class' : 'profileTable'})
		jobs = []
		for table in all_tables:
			work = {
					'Employer' : None,
					'Position' : None,
					'Time Period' : None,
					'Location' : None,
					'Description' : None,
				}
			trs = table.findAll('tr')
			for tr in trs:
				key = self._noColon(tr.td.string)
				anchor = tr.find('a')
				if anchor: # linked
					work[key] = anchor.string
				else: # not linked
					work[key] = tr.find('div').string
			jobs.append(F_Work(
				employer=work['Employer'], 
				position=work['Position'], 
				duration=work['Time Period'], 
				location=work['Location'], 
				description=work['Description']))
		self.attrs.update({'Work' : jobs})

	def getEducationAndWorkInfo(self):
		""" Add education and work info to the attr hash. """
		if self.boxes.has_key('Education and Work'):
			root = self.boxes['Education and Work']
			sibblings  = root.findAll('h4')[0].findNextSiblings()
			edu_table = sibblings[0]
			work_tables = [tag for tag in sibblings[2:] if tag.name == 'table']
			self.getEducationInfo(edu_table)
			self.getWorkInfo(work_tables)
		elif self.boxes.has_key('Education'):
			self.getEducationInfo()
		elif self.boxes.has_key('Work'):
			self.getWorkInfo()
		else:
			raise lf_base.FacebookAttributeFetchError, 'Education and Work'

	def getGroups(self):
		""" Add group info to the attr hash """
		try:
			root = self.boxes['Groups']
		except:
			raise lf_base.FacebookAttributeFetchError, 'Groups'
		groups = [a.string for a in root.findAll('a')[4:]]
		self.attrs.update({'Groups' : groups})

	def getContactAndPersonalInfo(self, get_cont=True, get_pers=True):
		""" Add contact and personal info to the attr hash """
		try:
			root = self.boxes['Information']
		except:
			raise lf_base.FacebookAttributeFetchError, 'Information'
		information = {}
		contact_info, personal_info = root.findAll('table', {'class' : 'profileTable'})

		if get_cont:
			c_trs = contact_info.findAll('tr')
			for tr in c_trs:
				key = self._noColon(tr.td)
				imgs = tr.findAll('img')
				if len(imgs): # Email(s)
					value = [self.ocr.read(i['src']) for i in imgs]
				elif key.find("Web") != -1: # Website
					value = [a['href'] for a in tr.findAll('a')]
				elif key.find("Current") != -1: # Current town/address
					value = self._defaultJoin([a.string for a in tr.findAll('a')])
				else:
					a_s = tr.findAll('a') 
					las = len(a_s)
					if las == 0:
						value = tr.div.string # phone #s, Google Chat, etc.
					elif las == 1:
						value = a_s[0].string # AIM
					else:
						value = [a.string for a in a_s]
				information[key] = value

		if get_pers:
			p_trs = personal_info.findAll('tr')
			for tr in p_trs:
				anchor = tr.find('a', {'href' : '#'})
				if anchor: # linked
					key = self._noColon(anchor.string)
				else: # not linked
					key = self._noColon(tr.td.string)
				data = tr.findAll('td')[1]
				a_s = data.findAll('a')
				if len(a_s): # linked
					value = self._defaultJoin([a.string for a in a_s])
				else: # not linked
					items = [i for i in data.div.contents]
					for i, item in enumerate(items):
						if not isinstance(item, unicode):
							items[i] = '\n'
					value = ''.join(items)
				information[key] = value

		self.attrs.update(information)


class F_Work(object):
	""" Represents a professional endeavor. """
	def __init__(self, **kwargs):
		self.kwargs = kwargs

	def __repr__(self):
		return '<F_Work: %s>' % self.kwargs


class F_School:
	""" Represents a scholastic endeavor. """
	def __init__(self, **kwargs):
		self.kwargs = kwargs

	def __repr__(self):
		return '<F_School: %s>' % self.kwargs


if __name__ == '__main__':
	profile_path = './profiles/danny.html' # an html profile page downloaded from Facebook
	f = file(profile_path)
	lines = f.readlines()
	f.close()

	p = FacebookProfilePage(''.join(lines))
	p.parsePage()

	# print sorted list of box names
	boxes = p.boxes.keys()
	boxes.sort()
	for box in boxes:
		print box
		
	print "==================================================="

	# get some more stuff from the page
	p.getEducationAndWorkInfo()
	p.getGroups()
	p.getContactAndPersonalInfo()

	# print sorted list of attributes
	attrs = p.attrs
	keys = attrs.keys()
	keys.sort()
	for k in keys:
		print "%-20s%s" % (k, attrs[k])

